export * from "./uni"
import { assign, isNumber, isNS, isObj, isString, nsa } from "./uni";

const lv = {
  /** 等于 */
  "="(v: any) { return isNS(v); },
  eq(v: any) { return isNS(v); },
  /** 不等于 */
  "<>"(v: any) { return isNS(v); },
  ne(v: any) { return isNS(v); },
  /** 大于 */
  ">"(v: any) { return isNS(v); },
  gt(v: any) { return isNS(v); },
  /** 大于等于 */
  ">="(v: any) { return isNS(v); },
  ge(v: any) { return isNS(v); },
  /** 小于 */
  "<"(v: any) { return isNS(v); },
  lt(v: any) { return isNS(v); },
  /** 小于等于 */
  "<="(v: any) { return isNS(v); },
  le(v: any) { return isNS(v); },
  /** 值是否在指定的范围内 */
  between(v: any): v is [number, number] { return v && Array.isArray(v) && v.length == 2 && isNumber(v[0]) && isNumber(v[1]) && v[0] < v[1]; },
  notBetween(v: any): v is [number, number] { return v && Array.isArray(v) && v.length == 2 && isNumber(v[0]) && isNumber(v[1]) && v[0] < v[1]; },
  /** 用于简单模式匹配，支持通配符 %（代表零个或多个任意字符）和 _（代表单个任意字符） */
  like(v: any) { return isString(v); },
  notLike(v: any) { return isString(v); },
  isNull(..._: any) { return true; },
  isNotNull(..._: any) { return true; },
  /** 值是否属于指定的一组值中 */
  in(v: any): v is (ns[]) {
    if (v && Array.isArray(v)) {
      if (!v.length) {
        return true;
      }
      const t = typeof v[0];
      if (nsa.includes(t as any)) {
        return !v.find(e => typeof e != t);
      }
    }
    return false;
  },
  /** 值是否不属于指定的一组值中 */
  notIn(v: any): v is (ns[]) {
    if (v && Array.isArray(v)) {
      if (!v.length) {
        return true;
      }
      const t = typeof v[0];
      if (nsa.includes(t as any)) {
        return !v.find(e => typeof e != t);
      }
    }
    return false;
  },
  /** 正则匹配 */
  regexp(v: any) { return isString(v); },
  /** 安全等于（NULL-safe equal），即使两边的操作数之一为 NULL，也能得到明确的结果（NULL 对 NULL 返回 1，NULL 对非 NULL 返回 0） */
  "<=>"(v: any) { return isNS(v); },
} as const;

/** 查询规则 */
export class QueryRule<T> {
  /** 连接关系 */
  or?: any;
  /** 字段(被比较值) */
  field: Key<T>;
  /** 比较关系 */
  logic: LK;
  /** 比较值(由于ts自身问题,当逻辑类型为having时, 数组第二个数据的数据类型只能通过显示定义才能约束) */
  value: Logic<Key<T>>[LK];
  /** 子逻辑关系 */
  readonly children: QueryRule<T>[] = [];
  constructor (opt?: R<T>) {
    if (opt) {
      this.field = opt.field;
      this.logic = opt.logic;
      this.value = opt.value;
      if (opt.or) { this.or = 1; }
      if (opt.children && Array.isArray(opt.children)) {
        opt.children.forEach(e => {
          const t = QueryRule.create(e);
          if (t) {
            this.children.push(t);
          }
        });
      }
    }
  }
  get validatorShallow(): QueryRule<T> | undefined {
    return lv[this.logic] && lv[this.logic](this.value) ? this : undefined;
  }
  get validator(): QueryRule<T> | undefined {
    if (this.clearErr()) {
      return this;
    }
  }
  append(opt: R<T>) {
    const r = QueryRule.create(opt);
    if (r) {
      this.children.push(r);
    }
    return r;
  }
  public static create<T>(opt: R<T>) {
    return new QueryRule(opt).validator;
  }
  toJSON() {
    // if (this.validator) {
    if (this.validator) {
      return {
        field: this.field, logic: this.logic, value: this.value,
        or: this.or ? 1 : undefined,
        children: this.children.length ? this.children : undefined
      };
    }
  }
  clearErr(p = this): QueryRule<T> | undefined {
    if (p.validatorShallow) {
      let l = p.children.length;
      while (l-- > 0) {
        const e = p.children[l];
        if (!e.clearErr(e as any)) {
          p.children.splice(l, 1);
        }
      }
      return p;
    }
  }
}
export function parseRules<T>(p?: R<T>[]) {
  if (p) {
    if (p && Array.isArray(p)) {
      const r: QueryRule<T>[] = [];
      let t: undefined | QueryRule<T> = undefined;
      p.forEach(e => {
        if (e instanceof QueryRule) {
          if ((t = e.validator)) {
            r.push(t);
          }
        } else if (t = QueryRule.create(e)) {
          r.push(t);
        }
      });
      if (r.length) {
        return r;
      }
    }
  }
}
export function validatorRules<T>(p?: QueryRule<T>[]) {
  const r: QueryRule<T>[] = [];
  if (p && Array.isArray(p)) {
    p.forEach(e => {
      const t = e.validator;
      if (t) { r.push(t); }
    });
  }
  return r.length ? r : undefined;
}
export class Query<T> {
  #page?: { current?: number; size?: number; };
  #order?: QueryOrder<T>[];
  #group?: QueryGroup<T>[];
  #rule?: QueryRule<T>[];
  #field?: Alias<T>;
  #entity?: T;
  constructor (entity?: QueryParam<T>) {
    if (entity) {
      if (entity["#rule"]) {
        this.setRule(entity["#rule"]);
      } else {
        this.#entity = entity as any;
      }
      if (entity["#page"]) {
        this.setPage(entity["#page"].current, entity["#page"].size);
      }
      if (entity["#order"]) {
        this.setOrder(entity["#order"]);
      }
      if (entity["#group"]) {
        this.setGroup(entity["#group"]);
      }
      if (entity["#field"]) {
        this.#field = entity["#field"];
      }
    }
  }
  setPage(current = 1, size = 10) {
    if (!(current > 1)) { current = 1; }
    if (!(size > 0)) { size = 10; }
    if (size > 2000) { size = 100; }
    this.#page = { current, size };
  }
  setOrder(p?: QueryOrder<T>[]) {
    if (p && Array.isArray(p)) {
      this.#order = p.filter(e => ["ASC", "DESC"].includes(e[1]));
      if (!this.#order.length) { this.#order = undefined; }
    } else {
      this.#order = undefined;
    }
  }
  setGroup(p?: QueryGroup<T>[]) {
    if (p && Array.isArray(p)) {
      this.#group = p.filter(e => isString(e));
      if (!this.#group.length) { this.#group = undefined; }
    } else {
      this.#group = undefined;
    }
  }
  setRule(p?: R<T>[]) {
    this.#rule = parseRules(p);
  }
  get validator() {
    this.#rule = validatorRules(this.#rule);
    return this.#rule;
  }
  toJSON() {
    const r: { [k: string]: any; } = this.#entity || {};
    if (this.#page) r["#page"] = this.#page;
    if (this.#order) r["#order"] = this.#order;
    if (this.#group) r["#group"] = this.#group;
    if (this.#rule) r["#rule"] = this.#rule;
    if (this.#field) r["#field"] = this.#field;
    return r;
  }
}
export class Update<T> {
  #rule?: QueryRule<T>[];
  #entity: Partial<T>;
  constructor (entity: Partial<T>, rule?: R<T>[]) {
    this.#entity = entity;
    this.#rule = parseRules(rule);
  }
  get validator() {
    this.#rule = validatorRules(this.#rule);
    return this.#rule;
  }
  toJSON() {
    const ret = {};
    if (isObj(this.#entity)) {
      if (this.validator) {
        assign(ret, { "#rule": this.#rule });
      } else if (!this.#entity["id"] && !isNS(this.#entity["id"])) {
        return ret;
      }
      assign(ret, this.#entity);
    }
    return ret;
  }
}